<?php

namespace Upfor\UpforPHP;

use Upfor\UpforPHP\Interfaces\CacheInterface;

/**
 * Session
 */
class Session {

    /**
     * Has the session been started yet?
     *
     * @var boolean
     */
    protected $started = false;

    /**
     * Has the session been destroyed?
     *
     * @var boolean
     */
    protected $destroyed = false;

    /**
     * Data TTL in seconds
     *
     * @var integer
     */
    protected $dataTTL = 1800;

    /**
     * Cookie TTL in seconds
     *
     * @var integer
     */
    protected $cookieTTL = 0;

    /**
     * Cookie name
     *
     * @var string
     */
    protected $cookieName = 'SID';

    /**
     * Session id
     *
     * @var string
     */
    protected $sessionId;

    /**
     * Session data
     *
     * @var array
     */
    protected $sessionData = [];

    /**
     * Store driver
     *
     * @var CacheInterface
     */
    protected $storeDriver;

    /**
     * Constructor
     *
     * @param array $options Session options
     */
    public function __construct(array $options = []) {
        if (!empty($options['storeDriver'])) {
            $this->storeDriver = cacheDriver($options['storeDriver']);
        } else {
            $this->storeDriver = cacheDriver();
        }

        !empty($options['dataTTL']) && $this->dataTTL = $options['dataTTL'];
        !empty($options['cookieTTL']) && $this->cookieTTL = $options['cookieTTL'];
        !empty($options['cookieName']) && $this->cookieName = $options['cookieName'];

        $this->start();
    }

    /**
     * Destructor
     */
    public function __destruct() {
        // Write session data
        if ($this->started && !$this->destroyed) {
            $this->storeDriver->set($this->sessionId, $this->sessionData, $this->dataTTL);
        }

        // Garbage collection
        //if (mt_rand(1, 100) === 100) {
        //    $this->storeDriver->gc($this->dataTTL);
        //}
    }

    /**
     * Returns the session id
     *
     * @return string
     */
    public function getSessionId() {
        return $this->sessionId;
    }

    /**
     * Regenerate the session id and returns it
     *
     * @param  boolean $keepOld Keep the session data associated with the old session id?
     * @return string
     */
    public function regenerateId($keepOld = false) {
        // Delete old data if we don't want to keep it
        if (!$keepOld) {
            $this->storeDriver->delete($this->sessionId);
        }

        // Generate a new id and set a new cookie
        $this->sessionId = $this->generateId();
        $this->setCookie();

        // Return the new session id
        return $this->sessionId;
    }

    /**
     * Returns all the session data
     *
     * @return array
     */
    public function all() {
        return $this->sessionData;
    }

    /**
     * Store a value in the session
     *
     * @param string $key   Session key
     * @param mixed  $value Session data
     */
    public function set($key, $value) {
        $this->sessionData[$key] = $value;
    }

    /**
     * Returns TRUE if key exists in the session and FALSE if not
     *
     * @param  string $key Session key
     * @return boolean
     */
    public function has($key) {
        return isset($this->sessionData[$key]);
    }

    /**
     * Returns a value from the session
     *
     * @param  string     $key     Session key
     * @param  null|mixed $default Default value
     * @return null|mixed
     */
    public function get($key, $default = null) {
        return isset($this->sessionData[$key]) ? $this->sessionData[$key] : $default;
    }

    /**
     * Removes a value from the session
     *
     * @param string $key Session key
     */
    public function delete($key) {
        unset($this->sessionData[$key]);
    }

    /**
     * Clears all session data
     */
    public function clear() {
        $this->sessionData = [];
    }

    /**
     * Destroys the session
     */
    public function destroy() {
        $this->storeDriver->delete($this->sessionId);

        cookie($this->cookieName, null);

        $this->destroyed = true;
    }

    /**
     * Starts the session
     */
    protected function start() {
        // Set the started flag to true
        $this->started = true;

        // Get the session id from the cookie or generate a new one if it doesn't exist
        $this->sessionId = request()->cookie($this->cookieName);
        if (!$this->sessionId) {
            $this->sessionId = $this->generateId();
        }

        // Create a new / update the existing session cookie
        $this->setCookie();

        // Load the session data
        $this->loadData();
    }

    /**
     * Generates a session id
     *
     * @return string
     */
    protected function generateId() {
        return md5(uniqid('', true) . request()->getRemoteIp() . microtime(true) . mt_rand(0, 9999));
    }

    /**
     * Adds a session cookie to the response
     */
    protected function setCookie() {
        $ttl = $this->cookieTTL === 0 ? 0 : $this->cookieTTL + time();

        cookie($this->cookieName, $this->sessionId, $ttl);
    }

    /**
     * Loads the session data
     */
    protected function loadData() {
        $data = $this->storeDriver->get($this->sessionId);

        $this->sessionData = $data === false ? [] : $data;
    }

}
